package com.zhang.boot.core.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhang.boot.core.constants.ApiConstant;
import com.zhang.boot.core.constants.ErrorEnum;
import com.zhang.boot.core.entity.Menu;
import com.zhang.boot.core.entity.RoleMenu;
import com.zhang.boot.core.entity.User;
import com.zhang.boot.core.entity.UserRole;
import com.zhang.boot.core.mapper.MenuMapper;
import com.zhang.boot.core.service.MenuService;
import com.zhang.boot.core.service.RoleMenuService;
import com.zhang.boot.core.service.UserRoleService;
import com.zhang.boot.core.util.AssertUtil;
import com.zhang.boot.core.vo.router.MetaVo;
import com.zhang.boot.core.vo.router.RouterVo;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    private final UserRoleService userRoleService;
    private final RoleMenuService roleMenuService;

    @Override
    public List<Menu> listByUser(User user) {
        AssertUtil.notEmpty(user, ErrorEnum.ILLEGAL_ACCESS);
        Integer userId = user.getUserId();
        return listByUserId(userId);
    }

    @Override
    public List<Menu> listByUserId(Integer userId) {
        List<Menu> result = new ArrayList<>();
        if (userId == null || userId <= 0) {
            return result;
        }
        Set<Integer> menuIdSet = listMenuIdsByUserId(userId);
        if (CollUtil.isEmpty(menuIdSet)) {
            return new ArrayList<>();
        }
        return listByIds(menuIdSet);
    }

    /**
     * 获取menuIds通过userId
     *
     * @param userId
     * @return
     */
    private Set<Integer> listMenuIdsByUserId(Integer userId) {
        Set<Integer> menuIdSet = new HashSet<>();
        //获取用户具有的角色列表
        List<UserRole> userRoleList = userRoleService.listByUserId(userId);
        if (CollUtil.isEmpty(userRoleList)) {
            return menuIdSet;
        }
        Set<Integer> roleIdList = userRoleList.stream()
                .map(UserRole::getRoleId)
                .collect(Collectors.toSet());
        List<RoleMenu> roleMenuList = roleMenuService.listByRoleIdList(roleIdList);
        if (CollUtil.isEmpty(roleMenuList)) {
            return menuIdSet;
        }
        menuIdSet = roleMenuList.stream()
                .map(RoleMenu::getMenuId)
                .collect(Collectors.toSet());
        return menuIdSet;
    }


    @Override
    public Menu getByMenuName(String menuName) {
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getMenuName, menuName);
        return getOne(queryWrapper);
    }

    @Override
    public List<Menu> listTreeByUser(User user) {
        if (user == null) {
            return new ArrayList<>();
        }
        if (user.isAdmin()) {
            return listTree(ApiConstant.ZERO);
        }
        return listTree(user.getUserId());
    }

    @Override
    public List<RouterVo> buildMenus(List<Menu> menus) {
        List<RouterVo> routers = new LinkedList<>();
        for (Menu menu : menus) {
            RouterVo router = RouterVo.builder()
                    .hidden(menu.isVisible())
                    .name(getRouteName(menu))
                    .path(getRouterPath(menu))
                    .componentPath(getComponent(menu))
                    .meta(new MetaVo(menu.getMenuName(), menu.getIcon()))
                    .build();
            List<Menu> children = menu.getChildren();
            if (CollUtil.isNotEmpty(children) && ApiConstant.MenuConstant.TYPE_DIR.equals(menu.getMenuType())) {
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(children));
            } else if (isMenuFrame(menu)) {
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo routerVo = new RouterVo();
                routerVo.setPath(menu.getPath());
                routerVo.setComponentPath(menu.getComponent());
                routerVo.setName(StringUtils.capitalize(menu.getPath()));
                routerVo.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
                childrenList.add(routerVo);
                router.setChildren(childrenList);
            }
            routers.add(router);
        }
        return routers;
    }

    /**
     * 获取组件信息
     *
     * @param menu
     * @return
     */
    private String getComponent(Menu menu) {
        String component = ApiConstant.MenuConstant.LAYOUT;
        if (StrUtil.isNotBlank(menu.getComponent()) && !isMenuFrame(menu)) {
            component = menu.getComponent();
        }
        return component;
    }

    /**
     * 获取路由地址
     *
     * @param menu
     * @return
     */
    public String getRouterPath(Menu menu) {
        String routerPath = menu.getPath();
        if (ApiConstant.ZERO == menu.getParentId() && ApiConstant.MenuConstant.TYPE_DIR.equalsIgnoreCase(menu.getMenuType()) &&
                ApiConstant.ONE == menu.getIsFrame()) {
            routerPath = "/" + menu.getPath();
        } else if (isMenuFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 获取路由名
     *
     * @param menu
     * @return
     */
    public String getRouteName(Menu menu) {
        String routerName = StringUtils.capitalize(menu.getPath());
        if (isMenuFrame(menu)) {
            routerName = ApiConstant.EMPTY;
        }
        return routerName;
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isMenuFrame(Menu menu) {
        return menu.getParentId() == 0 && ApiConstant.MenuConstant.TYPE_MENU.equals(menu.getMenuType())
                && menu.getIsFrame() == ApiConstant.ONE;
    }

    /**
     * 获取树形菜单
     *
     * @param userId
     * @return
     */
    public List<Menu> listTree(Integer userId) {
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        if (userId != null && userId > 0) {
            Set<Integer> menuIds = listMenuIdsByUserId(userId);
            queryWrapper.in(Menu::getMenuId, menuIds);
        }
        queryWrapper.in(Menu::getMenuType, getTreeCondition());
        queryWrapper.orderByAsc(Menu::getParentId, Menu::getOrderNum);
        List<Menu> menus = list(queryWrapper);
        return getChildMenus(menus, ApiConstant.ZERO);
    }

    /**
     * 获取子菜单
     *
     * @param menus
     * @param parentId
     * @return
     */
    private List<Menu> getChildMenus(List<Menu> menus, int parentId) {
        List<Menu> result = new ArrayList<>();
        for (Iterator<Menu> iterator = menus.iterator(); iterator.hasNext(); ) {
            Menu t = iterator.next();
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == parentId) {
                recursionFn(menus, t);
                result.add(t);
            }
        }
        return result;
    }

    /**
     * 递归子菜单
     *
     * @param list
     * @param menu
     */
    private void recursionFn(List<Menu> list, Menu menu) {
        // 得到子节点列表
        List<Menu> childList = getChildList(list, menu);
        menu.setChildren(childList);
        for (Menu tChild : childList) {
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private List<Menu> getChildList(List<Menu> list, Menu t) {
        List<Menu> tlist = new ArrayList<>();
        Iterator<Menu> it = list.iterator();
        while (it.hasNext()) {
            Menu n = it.next();
            if (n.getParentId() == t.getMenuId()) {
                tlist.add(n);
            }
        }
        return tlist;
    }

    /**
     * 判断是否有子节点
     */
    private boolean hasChild(List<Menu> list, Menu t) {
        return getChildList(list, t).size() > 0 ? true : false;
    }

    private List<Character> getTreeCondition() {
        List<Character> menus = new ArrayList<>();
        menus.add('M');
        menus.add('C');
        return menus;
    }
}
